package com.woniuxy;

import com.ai.WeatherAsk;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author: 马宇航
 * @Description: 调用大模型，先确保聊天可以进行
 * @DateTime: 25/09/05/星期五 16:24
 * @Component: 成都蜗牛学苑
 **/
@SpringBootTest
public class DashscopeHttpCall {
    //final修饰的属性，但是，允许构造赋值
    private static final String DASH_SCOPE_API_KEY=System.getenv("DASHSCOPE_API_KEY");
    private static final String URL_API="https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions";
    private static final String MODEL="qwen3-coder-plus";
    //支持并发 key 是 函数，value是对应的类对象 （后期，他就是底层源码的 函数注册方法）
    private static final Map<String,Class> functionMap = new ConcurrentHashMap<>();
//    public DashscopeHttpCall() {
//        //从环境变量中获取 apikey
//        this.DASH_SCOPE_API_KEY = System.getenv("DASHSCOPE_API_KEY");
//    }
    //开发一个方法，用来发送我们的消息
    public static String sendMessage(String message) throws IOException, InterruptedException {
        //1.先拼接消息 %s  %s 字符串占位符
        String json= """
                {
                    "model": "%s",
                    "messages": [
                          {
                                "role": "system",
                                "content": "提示词，你是一个智能助手，你需要根据用户的问题，来回答用户的问题。"
                            },
                        {
                            "role": "user",
                            "content": "%s"
                        }
                    ]
                }
                """.formatted(MODEL,message); //formatted可以根据顺序来替换这个占位符
        //构建请求方式，第一次，是用不来
        HttpRequest httpRequest = HttpRequest.newBuilder()
                .header("Authorization", "Bearer " + DASH_SCOPE_API_KEY)
                .header("Content-Type", "application/json")
                .uri(URI.create(URL_API))
                .POST(HttpRequest.BodyPublishers.ofString(json)) //必须封装
                .build();
        //需要提供http客户端，来发送上面的内容   （整篇代码记住 HttpClient单词，就可以玩了。）
        HttpClient httpClient = HttpClient.newHttpClient();
        //第一个参数，是用来发送上面封装的请求
        //第二个参数，是用来处理响应的回调函数（目前不用纠结）
        HttpResponse<String> response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
        //最后一步，JSON字符串，转成JSON对象  为什么要选择JonNode，因为可以一直.取内容
        JsonNode jsonNode = new ObjectMapper().readTree(response.body());
        System.out.println(jsonNode.get("choices").get(0).get("message").get("content"));
        return jsonNode.get("choices").get(0).get("message").get("content").asText();
    }

    //工具调用:写死！不灵活
    public static String toolCall(String message) throws IOException, InterruptedException {
        //1.先拼接消息 %s  %s 字符串占位符
        String json= """
                {
                    "model": "%s",
                    "stream": false,
                    "messages": [
                               {
                                    "role": "system",
                                    "content": "提示词，你是一个智能助手，你需要根据用户的问题，来回答用户的问题和调用函数。"
                                },
                                {
                                    "role": "user",
                                    "content": "%s"
                                }
                    ],
                    "tools":[
                        {
                            "type": "function",
                            "function": {
                                "name": "ask",
                                "description": "当你想知道现在的天气非常有用。",
                                "parameters": {}
                            }
                        },
                        {
                            "type": "function",
                            "function": {
                                "name": "ask",
                                "description": "当你想查询指定城市的天气时非常有用。",
                                "parameters": {
                                    "type": "object",
                                    "properties": {
                                        "city": {
                                            "type": "string",
                                            "description": "城市或县区，比如北京市、杭州市、余杭区等。"
                                        }
                                    },
                                    "required": ["city"]
                                }
                            }
                        }
                    ]
                }
                """.formatted(MODEL,message); //formatted可以根据顺序来替换这个占位符
        System.out.println(json);
        //构建请求方式，第一次，是用不来
        HttpRequest httpRequest = HttpRequest.newBuilder()
                .header("Authorization", "Bearer " + DASH_SCOPE_API_KEY)
                .header("Content-Type", "application/json")
                .uri(URI.create(URL_API))
                .POST(HttpRequest.BodyPublishers.ofString(json)) //必须封装
                .build();

        //需要提供http客户端，来发送上面的内容   （整篇代码记住 HttpClient单词，就可以玩了。）
        HttpClient httpClient = HttpClient.newHttpClient();
        //第一个参数，是用来发送上面封装的请求
        //第二个参数，是用来处理响应的回调函数（目前不用纠结）
        HttpResponse<String> response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
        //最后一步，JSON字符串，转成JSON对象  为什么要选择JonNode，因为可以一直.取内容
        JsonNode jsonNode = new ObjectMapper().readTree(response.body());
        //新的第一步：ai回复的消息，可能会触发函数
        if(!jsonNode.findPath("tool_calls").isEmpty()){
            //第二步，触发反射调用函数 返回值应该是 方法返回的json字符串
            String result = callMyFunctionMethod(jsonNode);
            return result;
        }else {
            System.out.println(jsonNode);
            return jsonNode.asText();
        }
    }
    //执行函数调用
    private static String callMyFunctionMethod(JsonNode jsonNode) {
        //第三步，从jsonNode中，提取函数名
        String functionName = jsonNode.findPath("tool_calls").get(0).get("function").get("name").asText();
        //第四步，有没有定义自己的Map容器，存放，方法与类的对应关系。
        Class aClass = functionMap.get(functionName);
        try {
            //第五步，类对象没法调用普通函数，实例化这个对象
            Object o = aClass.newInstance();
            //第六步，有函数名字，以后类对象，判断有没有参数（重载的概念还在）
            //第七步，判断有没有参数
            if(jsonNode.findPath("tool_calls").get(0).get("function").get("arguments").asText().equals("{}")){
                //没有参数，
                Method method = aClass.getMethod(functionName);
                //第八步，调用方法 方法Method.invode(实例化对象) 就可以执行方法 res 就是返回值
                Object res = method.invoke(o);
                return res.toString();
            }else{
                //有参数: 但是暂时写死，同时没有考虑参数类型！这个东西自己封装下会好用点。
                System.out.println(jsonNode);
                //"{\"city\": \"成都市\"}"  text字符串
                JsonNode arguments = jsonNode.findPath("arguments");
                //{"city": "成都市"} 转成对象
                JsonNode jsonObj = new ObjectMapper().readTree(arguments.asText());
                //arguments是文本而不是对象
                Iterator<String> fields = jsonObj.fieldNames();
                //需要考虑，这个参数的名字对应的方法能否反射获取到
                while(fields.hasNext()){
                    String fieldName = fields.next();
                    JsonNode value = jsonObj.get(fieldName);
                    //需要判断参数类型
                    Object res = aClass.getMethod(functionName, String.class).invoke(o, value.asText());
                    return res.toString();
                }
                return null;
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    @Test
    public void main() throws IOException, InterruptedException {
        //以后是外部注册，这里暂时先内部注册
        functionMap.put("ask", WeatherAsk.class);
        String res = toolCall("你好，成都的天气如何？");
        System.out.println(res);
    }
}
